/*
Copyright (C) 2011 by Gary Wilber

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

// This project requires OpenSteamworks, and Google Voice API to compile

// If you don't know whats going on check out http://code.google.com/p/steam-chat-relay/

#define STEAMWORKS_CLIENT_INTERFACES
#pragma comment(lib, "C:/Programming/Steamworks/open-steamworks/Resources/Libs/Win32/steamclient.lib")
#include "C:/Programming/Steamworks/open-steamworks/Open Steamworks/Steamworks.h"

#include "../Google Voice API/GoogleVoice.h"

#include "boost/lexical_cast.hpp"
#include "boost/algorithm/string.hpp"

#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <map>
#include <sstream>
#include <time.h>

using namespace std;

IClientFriends *clientFriends = NULL;
ISteamFriends001 *steamFriends = NULL;

int current_id = 1;

struct friendData {
	string friend_id;
	bool chatRoom;
};

typedef map<CSteamID, friendData> friend_map;
friend_map friend_ids;

string stringToLower(string str)
{
	transform(str.begin(), str.end(), str.begin(), ::tolower);
	return str;
}

struct findFriendData {
	CSteamID friendSteamID;
	string name;
	int code;
};

findFriendData findFriend(string search, bool check = true)
{
	int found = 0;
	for(int i = 0; i <= steamFriends->GetFriendCount(); i++)
	{
		CSteamID friendSteamID = steamFriends->GetFriendByIndex(i);
		if(steamFriends->GetFriendPersonaState(friendSteamID) != k_EPersonaStateOffline)
		{
			string s_friendName(steamFriends->GetFriendPersonaName(friendSteamID));
			if(stringToLower(s_friendName).find(search) != string::npos)
			{
				if(check)
				{
					found++;
				}
				else
				{
					findFriendData data;
					data.code = 0;
					data.name = s_friendName;
					data.friendSteamID = friendSteamID;
					return data;
				}
			}
		}
	}
	if(found == 1)
	{
		return findFriend(search, false);
	}

	findFriendData data;

	if(found > 1)
	{
		data.code = 2;
	}
	else
	{
		data.code = 1;
	}

	return data;
}

int main()
{
	cout << "Steam Chat Relay" << endl;

	CSteamAPILoader loader;
	CreateInterfaceFn factory = loader.GetSteam3Factory();

	ISteamClient011 *steamClient = (ISteamClient011*)factory(STEAMCLIENT_INTERFACE_VERSION_011, NULL);
	if(!steamClient)
	{
		cout << "Error: Unable to load steamClient" << endl;
		return EXIT_FAILURE;
	}

	HSteamPipe steamPipe = steamClient->CreateSteamPipe();
	HSteamUser steamUser = steamClient->ConnectToGlobalUser(steamPipe);

	IClientEngine *engine = (IClientEngine*)factory("CLIENTENGINE_INTERFACE_VERSION002", NULL);
	if(!engine)
	{
		cout << "Error: Unable to load engine" << endl;
		return EXIT_FAILURE;
	}
	
	IClientUser *clientUser = engine->GetIClientUser(steamUser, steamPipe, CLIENTUSER_INTERFACE_VERSION);
	if(!clientUser)
	{
		cout << "Error: Unable to load clientUser" << endl;
		return EXIT_FAILURE;
	}

	clientFriends = engine->GetIClientFriends(steamUser, steamPipe, CLIENTFRIENDS_INTERFACE_VERSION);
	if(!clientFriends)
	{
		cout << "Error: Unable to load clientFriends" << endl;
		return EXIT_FAILURE;
	}

	steamFriends = (ISteamFriends001*)steamClient->GetISteamFriends(steamUser, steamPipe, STEAMFRIENDS_INTERFACE_VERSION_001);
	if(!steamFriends)
	{
		cout << "Error: Unable to load steamFriends" << endl;
		return EXIT_FAILURE;
	}

	ifstream config("steam_chat_relay_config.txt"); 
	if(!config)
	{ 
		cout << "Cannot open steam_chat_relay_config.txt file, please create the file" << endl; 
		return EXIT_FAILURE;
	} 

	string configLine;
	config >> configLine;

	vector<string> split;
	boost::split(split, configLine, boost::is_any_of(":"));

	if(split.size() != 3)
	{
		cout << "Wrong config format, use: email:password:cellphonenumber" << endl; 
		return EXIT_FAILURE;
	}

	string phoneNumber = split[2];

	GoogleVoice *gVoice = new GoogleVoice(split[0], split[1]);

	bool loggedIn = gVoice->Login();

	printf("Google Voice Login: %s\n", loggedIn ? "success" : "failure");

	if(!loggedIn)
	{
		return EXIT_FAILURE;
	}

	CSteamID mySteamID = clientUser->GetSteamID();

	CallbackMsg_t callbackMsg;

	sms_vector sms_messages;
	time_t nextCheckTime = 0;

	while(true)
	{
		if(nextCheckTime <= time(NULL))
		{
			nextCheckTime = time(NULL) + 3;

			sms_messages = gVoice->GetNewSMS();

			for(unsigned int i=0; i < sms_messages.size(); i++)
			{
				sms_data data = sms_messages[i];

				string message = data.message;

				cout << "Found New SMS: " << data.id << " | Length: " << message.length() << endl;
				//gVoice->MarkSMSRead(data.id);
				gVoice->DeleteSMS(data.id);
				
				if(message == "")
				{
					cout << "\tMessage Blank!?" << endl;
				}
				else if(stringToLower(message) == "exit")
				{
					cout << "\tExiting..." << endl;
					gVoice->SendSMS(phoneNumber, "Exiting...");
					return EXIT_FAILURE;
				}
				else if(message.substr(0, 1) == "0")
				{
					if(message.length() >= 3)
					{
						cout << "Message Command" << endl;

						string toSplit = message.substr(2, message.length());

						int spacePosition = toSplit.find_first_of(" ", 0);
						if(spacePosition != string::npos)
						{
							string search = stringToLower(toSplit.substr(0, spacePosition));
							string realMessage = toSplit.substr(spacePosition + 1, message.length());

							cout << "Search: " << search << " | Message: " << realMessage << endl;

							findFriendData data = findFriend(search);
							if(data.code == 2)
							{
								cout << "\tFound Multiple" << endl;
								gVoice->SendSMS(phoneNumber, "Unable to send message | Found multiple friend matches");
							}
							else if(data.code == 1)
							{
								cout << "\tFound None" << endl;
								gVoice->SendSMS(phoneNumber, "Unable to send message | Found no matches");
							}
							else
							{
								const char* pchMsgBody = realMessage.c_str();
								cout << "\tSent Message to " << data.name << endl;
								steamFriends->SendMsgToFriend(data.friendSteamID, k_EChatEntryTypeChatMsg, pchMsgBody, strlen(pchMsgBody));
							}
							
						}
					}
				}
				else if(message.length() >= 4 && stringToLower(message).substr(0, 4) == "list")
				{
					string search = "";
					if(message.length() >= 6)
					{
						search = stringToLower(message.substr(5, message.length()));
					}

					cout << "List Command: Search: " << search << endl;

					string friendList = "Online List: ";

					for(int i = 0; i <= steamFriends->GetFriendCount(); i++)
					{
						CSteamID friendSteamID = steamFriends->GetFriendByIndex(i);
						if(steamFriends->GetFriendPersonaState(friendSteamID) != k_EPersonaStateOffline)
						{
							string s_friendName(steamFriends->GetFriendPersonaName(friendSteamID));
							if(search == "" || stringToLower(s_friendName).find(search) != string::npos)
							{
								friendList += s_friendName + ", ";
							}
						}
					}

					friendList = friendList.substr(0, friendList.length() - 2);

					gVoice->SendSMS(phoneNumber, friendList);

					/*
					cout << friendList << (friendList.length() / 160) << endl;

					int count = 0;
					for(unsigned int i = 0; i < (friendList.length() / 160) - 1; i++)
					{
						cout << "SENDING: " << friendList.substr(count, min(count + 160, friendList.length())) << endl;
						gVoice->SendSMS(phoneNumber, friendList.substr(count, min(count + 160, friendList.length())));

						if(count + 160 >= friendList.length())
						{
							break;
						}

						count += 160;
					}
					*/
				}
				else if(message.length() > 2)
				{
					string s_friend_id = message.substr(0, 1);

					cout << "\tFriend ID: " << s_friend_id << endl;

					friend_map::iterator it;

					bool found = false;

					for(it=friend_ids.begin() ; it != friend_ids.end(); it++)
					{
						friendData data = (*it).second;
						if(data.friend_id == s_friend_id)
						{
							found = true;
							CSteamID friendSteamID = (*it).first;
							if(data.chatRoom || steamFriends->GetFriendPersonaState(friendSteamID) != k_EPersonaStateOffline)
							{
								string realMessage = message.substr(2, message.length());

								cout << "\tRelaying Message: " << realMessage << endl;

								const char* pchMsgBody = realMessage.c_str();

								if(data.chatRoom)
								{
									clientFriends->SendChatMsg(friendSteamID, k_EChatEntryTypeChatMsg, pchMsgBody, strlen(pchMsgBody));
								}
								else
								{
									steamFriends->SendMsgToFriend(friendSteamID, k_EChatEntryTypeChatMsg, pchMsgBody, strlen(pchMsgBody));
								}
							}
							else
							{
								string s_friendName(steamFriends->GetFriendPersonaName(friendSteamID));
								gVoice->SendSMS(phoneNumber, s_friend_id + " | Unable to send message | " + s_friendName + " is offline");
							}
							break;
						}
					}

					if(!found)
					{
						cout << "\t\tFailed to find friend id" << endl;
					}

				}
			}
		}

		if(Steam_BGetCallback(steamPipe, &callbackMsg))
		{
			bool groupChat = callbackMsg.m_iCallback == ChatRoomMsg_t::k_iCallback;
			if(callbackMsg.m_iCallback == FriendChatMsg_t::k_iCallback || groupChat)
			{
				FriendChatMsg_t *chatMsg = (FriendChatMsg_t*)callbackMsg.m_pubParam;

				EChatEntryType msgType;

				CSteamID sender = chatMsg->m_ulFriendID;

				string s_friendName(steamFriends->GetFriendPersonaName(chatMsg->m_ulSenderID));

				char pvData[2048];

				bool chatRoom = false;

				int ret = steamFriends->GetChatMessage(sender, chatMsg->m_iChatID, pvData, 2048, &msgType);
				if(ret == 0)
				{
					chatRoom = true;
					clientFriends->GetChatRoomEntry(sender, chatMsg->m_iChatID, &chatMsg->m_ulSenderID, pvData, 2048, &msgType);
				}

				string message = pvData;

				if(msgType == k_EChatEntryTypeChatMsg)
				{
					if(chatMsg->m_ulSenderID != mySteamID)
					{
						cout << "Received Message: " << message << endl;
						if(friend_ids[sender].friend_id.empty())
						{
							cout << "\tSetup Friend ID: " << current_id << endl;

							friendData data;
							data.chatRoom = chatRoom;
							data.friend_id = boost::lexical_cast<std::string>(current_id);

							friend_ids[sender] = data;
							current_id++;
						}

						cout << "\tSending SMS Message...";

						if(gVoice->SendSMS(phoneNumber, friend_ids[sender].friend_id + " | " + s_friendName + ": " + message))
						{
							cout << "Success";
						}
						else
						{
							cout << "Failed";
						}

						cout << endl;
					}
				}
			}
			/*else if(callbackMsg.m_iCallback == ChatRoomInvite_t::k_iCallback) TODO: Make the user choose to enter the chatroom or not.
			{
				ChatRoomInvite_t *chatRoomInvite = (ChatRoomInvite_t*)callbackMsg.m_pubParam;
				
				string s_friendName(steamFriends->GetFriendPersonaName(chatRoomInvite->m_ulSteamIDPatron));

				cout << "Received Chatroom Invite from " << s_friendName << endl;

				bool joined = clientFriends->JoinChatRoom(chatRoomInvite->m_ulSteamIDChat);
				if(joined)
				{
					cout << "\tJoined Chatroom" << endl;
					gVoice->SendSMS(phoneNumber, "Joined chatroom, you were invited by " + s_friendName);
				}
				else
				{
					cout << "\tFailed to join Chatroom" << endl;
					gVoice->SendSMS(phoneNumber, "Failed to join chatroom, you were invited by " + s_friendName);
				}
			}*/

			Steam_FreeLastCallback(steamPipe);
		}

		Sleep(10);
	}

	return EXIT_SUCCESS;
}
